﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
using System.Web;
using System.Web.Caching;

namespace Catalyst.Caching
{
	public class AspNetSqlCache : ObjectCache
	{
		Cache m_Cache;

		public AspNetSqlCache()
		{
			if (HttpContext.Current == null)
			{
				throw new Exception(String.Format("The {0} class can only be used in an ASP.NET compatible application.", typeof(AspNetSqlCache).FullName));
			}

			m_Cache = HttpContext.Current.Cache;
		}

		public CacheItem add(string key, object value, CacheItemPolicy policy)
		{
			bool hasAspNetSqlChangeMonitor = false;
			AggregateCacheDependency aggregateDependency = null;

			Action<ChangeMonitor> addDependency = (c) =>
			{
				CacheDependency dependency = null;

				if (c is AspNetSqlChangeMonitor)
				{
					hasAspNetSqlChangeMonitor = true;

					AspNetSqlChangeMonitor monitor = (AspNetSqlChangeMonitor)c;
					dependency = new SqlCacheDependency(monitor.DatabaseEntryName, monitor.TableName);
					aggregateDependency.Add(dependency);
				}
				else if (c is FileChangeMonitor)
				{
					FileChangeMonitor monitor = (FileChangeMonitor)c;

					Action<string> addFileDependency = (f) =>
					{
						dependency = new CacheDependency(f);
						aggregateDependency.Add(dependency);
					};

					monitor.FilePaths.ToList().ForEach(addFileDependency);
				}
			};

			policy.ChangeMonitors.ToList().ForEach(addDependency);

			if (!hasAspNetSqlChangeMonitor)
			{
				throw new Exception(String.Format("A {0} instance must contain at least 1 AspNetSqlChangeMonitor.", typeof(AspNetSqlCache).FullName));
			}

			DateTime absoluteExpiration = Cache.NoAbsoluteExpiration;
			if (policy.AbsoluteExpiration != ObjectCache.InfiniteAbsoluteExpiration)
			{
				absoluteExpiration = policy.AbsoluteExpiration.LocalDateTime;
			}

			TimeSpan slidingExpiration = Cache.NoSlidingExpiration;
			if (absoluteExpiration != Cache.NoAbsoluteExpiration && policy.SlidingExpiration != ObjectCache.NoSlidingExpiration)
			{
				slidingExpiration = policy.SlidingExpiration;
			}

			System.Web.Caching.CacheItemPriority priority = System.Web.Caching.CacheItemPriority.Normal;

			if (policy.Priority == System.Runtime.Caching.CacheItemPriority.NotRemovable)
			{
				priority = System.Web.Caching.CacheItemPriority.NotRemovable;
			}

			CacheItemRemovedCallback callback = null;
			if (policy.RemovedCallback != null)
			{
				callback = (k, s, r) =>
				{
					CacheEntryRemovedReason reason = CacheEntryRemovedReason.CacheSpecificEviction;

					switch (r)
					{
						case CacheItemRemovedReason.Expired: reason = CacheEntryRemovedReason.Expired; break;
						case CacheItemRemovedReason.Removed: reason = CacheEntryRemovedReason.Removed; break;
						case CacheItemRemovedReason.DependencyChanged: reason = CacheEntryRemovedReason.ChangeMonitorChanged; break;
						default: break;
					}

					policy.RemovedCallback.Invoke(new CacheEntryRemovedArguments(this, reason, new CacheItem(k, s)));
				};
			}

			return new CacheItem(key, m_Cache.Add(key, value, aggregateDependency, absoluteExpiration, slidingExpiration, priority, callback));
		}

		public override bool Add(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
		{
			try
			{
				add(key, value, new CacheItemPolicy { AbsoluteExpiration = absoluteExpiration });

				return true;
			}
			catch
			{
				return false;
			}
		}

		public override bool Add(CacheItem item, CacheItemPolicy policy)
		{
			try
			{
				add(item.Key, item.Value, policy);

				return true;
			}
			catch
			{
				return false;
			}
		}

		public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null)
		{
			return AddOrGetExisting(new CacheItem(key, value), policy).Value;
		}

		public override CacheItem AddOrGetExisting(CacheItem value, CacheItemPolicy policy)
		{
			string key = value.Key;
			object val = value.Value;

			if (Contains(key))
			{
				return GetCacheItem(key);
			}
			else
			{
				return add(key, val, policy);
			}
		}

		public override object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
		{
			if (Contains(key))
			{
				return m_Cache[key];
			}
			else
			{
				return m_Cache.Add(key, value, null, Cache.NoAbsoluteExpiration, absoluteExpiration.Offset, System.Web.Caching.CacheItemPriority.Normal, null);
			}
		}

		public override bool Contains(string key, string regionName = null)
		{
			return (m_Cache[key] != null);
		}

		public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable<string> keys, string regionName = null)
		{
			throw new NotImplementedException();
		}

		public override DefaultCacheCapabilities DefaultCacheCapabilities
		{
			get { return DefaultCacheCapabilities.AbsoluteExpirations 
				| DefaultCacheCapabilities.CacheEntryRemovedCallback 
				| DefaultCacheCapabilities.InMemoryProvider 
				| DefaultCacheCapabilities.SlidingExpirations; }
		}

		public override object Get(string key, string regionName = null)
		{
			return m_Cache[key];
		}

		public override CacheItem GetCacheItem(string key, string regionName = null)
		{
			return new CacheItem(key, m_Cache[key]);
		}

		public override long GetCount(string regionName = null)
		{
			return m_Cache.Count;
		}

		protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator()
		{
			return (IEnumerator<KeyValuePair<string, object>>)m_Cache.GetEnumerator();
		}

		public override IDictionary<string, object> GetValues(IEnumerable<string> keys, string regionName = null)
		{
			Dictionary<string, object> vals = new Dictionary<string, object>();

			Action<string> addValue = (key) =>
			{
				try
				{
					vals.Add(key, m_Cache[key]);
				}
				catch { }
			};

			keys.ToList().ForEach(addValue);

			return vals;
		}

		public override string Name
		{
			get { throw new NotImplementedException(); }
		}

		public override object Remove(string key, string regionName = null)
		{
			return m_Cache.Remove(key);
		}

		public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null)
		{
			throw new NotImplementedException();
		}

		public override void Set(CacheItem item, CacheItemPolicy policy)
		{
			throw new NotImplementedException();
		}

		public override void Set(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
		{
			throw new NotImplementedException();
		}

		public override object this[string key]
		{
			get
			{
				return m_Cache[key];
			}
			set
			{
				m_Cache[key] = value;
			}
		}
	}
}
